/*******************************************************************************
 * Copyright © 2012 Ben Swim
 *
 * This file is part of ImoSPC.
 *
 * ImoSPC is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * ImoSPC is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * ImoSPC. If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package net.smwcentral;

import java.util.*;
import java.util.Map.*;

// Represents an iterator which can be used to navigate a set freely.
interface NavigableIterator<E> extends Iterator<E> {
	// Checks if there is a previous item.
	boolean hasPrevious();
	// Go to the previous item.
	E previous() throws NoSuchElementException;
	
	// Go to the first item.
	E first() throws NoSuchElementException;
	// Go to the last item.
	E last() throws NoSuchElementException;
	
	// Get how many items there are.
	int count();
	// Get the current position, or -1 if iteration hasn't started yet.
	int position();
	// Set the position without getting an element.
	void setPosition(int pos);
	// Gets the current item.
	E current() throws NoSuchElementException;
	
	// Checks if an item is available.
	boolean hasItem(int index);
	// Go to the specified item.
	E get(int index) throws NoSuchElementException;
	
	// Check without navigating.
	E peek();
	E peekAt(int index);
	E peekBack();
	E peekFirst();
	E peekLast();
}

// Convenience interface; more aesthetically pleasing than using:
//  NavigableIterator<Entry<String, byte[]>>
// Instead, use:
//  NavigableMapIterator<String, byte[]>
interface NavigableMapIterator<K, V> extends NavigableIterator<Entry<K, V>> { }

// Represents a fixed-size, immutable hash map.
final class FixedMap<K, V> extends AbstractMap<K, V> {
	// Get the map of an iterator.
	static FixedMap getMapOfIterator(NavigableIterator iter) {
		return ((FixedNavigableIterator)iter).m_map;
	}
	
	// A fixed map set.
	private final static class FixedMapSet<K, V>
		extends AbstractSet<Entry<K, V>> {
		
		final int m_length;
		
		final int[] m_keys;
		final Entry<K, V>[] m_entries;
		final FixedMap<K, V> m_map;
		
		// Copy the items from a collection.
		@SuppressWarnings({"unchecked"})
		public FixedMapSet(List<Entry<K, V>> items, FixedMap<K, V> map) {
			m_length = items.size();
			
			m_keys = new int[m_length];
			m_entries = new Entry[m_length];
			
			m_map = map;
			
			for (int i = 0; i < m_length; i++) {
				Entry<K, V> entry = items.get(i);
				if (entry == null) {
					m_keys[i] = 0;
					m_entries[i] = null;
				} else {
					m_keys[i] = entry.getKey().hashCode();
					m_entries[i] = entry;
				}
			}
		}
		// Copy the items from a map.
		@SuppressWarnings({"unchecked"})
		public FixedMapSet(Map<K, V> items, FixedMap<K, V> map) {
			m_length = items.size();
			
			m_keys = new int[m_length];
			m_entries = new Entry[m_length];
			
			m_map = map;
			
			int i = 0;
			for (Iterator<Entry<K, V>> iter = items.entrySet().iterator();
														iter.hasNext(); i++) {
				Entry<K, V> entry = iter.next();
				if (entry == null) {
					m_keys[i] = 0;
					m_entries[i] = null;
				} else {
					m_keys[i] = entry.getKey().hashCode();
					m_entries[i] = entry;
				}
			}
		}
		// Copy a single item.
		@SuppressWarnings({"unchecked"})
		public FixedMapSet(Entry<K, V> item, FixedMap<K, V> map) {
			m_length = 1;
			
			m_keys = new int[] { item.getKey().hashCode() };
			m_entries = new Entry[] { item };
			
			m_map = map;
		}

		@Override public Iterator<Entry<K, V>> iterator() {
			return new FixedNavigableIterator<K, V>(this, m_map);
		}

		@Override public int size() { return m_length; }
	}
	
	// An immutable but navigable iterator.
	private final static class FixedNavigableIterator<K, V>
		implements NavigableMapIterator<K, V> {

		final FixedMap<K, V> m_map;
		final FixedMapSet<K, V> m_set;
		final int m_length;
		
		int m_pos;
		
		public FixedNavigableIterator(FixedMapSet<K, V> set, FixedMap<K, V> map) {
			m_map = map;
			
			m_set = set;
			m_length = set.m_length;
			
			m_pos = -1;
		}
		
		@Override public boolean hasPrevious() {
			return m_length > 0 && m_pos > 0;
		}

		@Override public Entry<K, V> previous() {
			if (!hasPrevious()) throw new NoSuchElementException();
			return m_set.m_entries[--m_pos];
		}

		@Override public Entry<K, V> first() {
			if (m_length <= 0) throw new NoSuchElementException();
			return m_set.m_entries[m_pos = 0];
		}

		@Override public Entry<K, V> last() {
			if (m_length <= 0) throw new NoSuchElementException();
			return m_set.m_entries[m_pos = m_length - 1];
		}

		@Override public int count() { return m_length; }
		@Override public int position() { return m_pos; }

		@Override
		public void setPosition(int pos) {
			if (pos < -1 || pos > m_length) {
				throw new IndexOutOfBoundsException();
			}
			m_pos = pos;
		}
		
		@Override public Entry<K, V> current() {
			if (m_length <= 0 || m_pos < 0 || m_pos >= m_length)
				throw new NoSuchElementException();
			return m_set.m_entries[m_pos];
		}

		@Override public boolean hasItem(int index) { return index < m_length; }
		
		@Override public Entry<K, V> get(int index) {
			if (!hasItem(index)) throw new NoSuchElementException();
			return m_set.m_entries[m_pos = index];
		}

		@Override public boolean hasNext() {
			return m_length > 0 && m_pos + 1 < m_length;
		}

		@Override public Entry<K, V> next() {
			if (!hasNext()) throw new NoSuchElementException();
			return m_set.m_entries[++m_pos];
		}

		@Override public void remove() {
			throw new UnsupportedOperationException("Not supported.");
		}

		@Override public Entry<K, V> peek() {
			if (!hasNext()) throw new NoSuchElementException();
			return m_set.m_entries[m_pos + 1];
		}

		@Override public Entry<K, V> peekAt(int index) {
			if (!hasItem(index)) throw new NoSuchElementException();
			return m_set.m_entries[index];
		}

		@Override public Entry<K, V> peekBack() {
			if (!hasPrevious()) throw new NoSuchElementException();
			return m_set.m_entries[m_pos - 1];
		}

		@Override public Entry<K, V> peekFirst() {
			return m_set.m_entries[0];
		}

		@Override public Entry<K, V> peekLast() {
			return m_set.m_entries[m_length - 1];
		}
	}

	private final FixedMapSet<K, V> m_set;
	
	public FixedMap(List<Entry<K, V>> items) {
		m_set = new FixedMapSet<K, V>(items, this);
	}
	
	public FixedMap(Map<K, V> items) {
		m_set = new FixedMapSet<K, V>(items, this);
	}
	
	public FixedMap(Entry<K, V> item) {
		m_set = new FixedMapSet<K, V>(item, this);
	}
	
	public FixedMap(K key, V value) {
		this(new HashMap.SimpleImmutableEntry<K, V>(key, value));
	}
	
	@Override public Set<Entry<K, V>> entrySet() {
		return m_set;
	}
	
	public NavigableMapIterator<K, V> iterator() {
		return new FixedNavigableIterator<K, V>(m_set, this);
	}
	
	// Get a positioned iterator.
	public NavigableMapIterator<K, V> iterator(int position) {
		FixedNavigableIterator<K, V> iter =
				new FixedNavigableIterator<K, V>(m_set, this);
		if (iter.hasItem(position))
			iter.get(position);
		return iter;
	}
	
	// Gets the index of a key.
	public int getIndex(K key) {
		int hash = key.hashCode();
		for (int i = 0; i < m_set.m_length; i++) {
			if (hash != m_set.m_keys[i]) continue;
			if (key.equals(m_set.m_entries[i].getKey()))
				return i;
		}
		// No such key
		return -1;
	}
}
